fig-financial-payments-b2b-xapi
Webhook Delivery – Encrypted Payment Status Notifications
Introduction
The Fidelidade sends real-time webhook notifications whenever a payment session changes status. Instead of polling the API, your system receives automatic HTTPS POST callbacks containing the updated status.
Because payment sessions may include sensitive information, webhooks are delivered using end-to-end encryption (AES-256/GCM). This ensures confidentiality and integrity of the data during transmission.
Each webhook contains:
- Encrypted payload (ciphertext) in the HTTP body
- Encryption metadata in headers:
X-IVX-AuthTagX-Idempotency-Keyreferencing the original payment intent
Encryption is performed using the same encryptionKey that was sent to the partner during the provisional receipt step.
Partners must store the following pair during the payment intent request:
- Idempotency-Key
- encryptionKey
These two elements are required to decrypt any webhook related to that payment.
Webhook Encryption Model
During the insurance contracting flow, the platform returns:
receiptToken— an opaque, unreadable token containing internal receipt dataencryptionKey— a Base64-encoded AES-256 key used to encrypt:- the payment intent payload sent by the partner
- the webhook payload sent back to the partner
For every webhook delivery:
- A new IV (12 bytes, Base64) is generated
- A new AuthTag (16 bytes → Base64) is produced
- The same
encryptionKeyassociated with the original receipt is reused - The actual JSON payload is encrypted with AES-256/GCM
GCM mode ensures confidentiality and tamper-proof integrity.
Webhook Request Structure
Required HTTP Headers
| Header | Description |
|---|---|
| X-Idempotency-Key | The same Idempotency-Key originally sent by the partner when creating the payment intent. Identifies the payment session. |
| X-IV | Base64-encoded AES-GCM Initialization Vector (12 bytes). |
| X-AuthTag | Base64-encoded AES-GCM authentication tag (16 bytes). |
| Authorization | Authentication method configured during onboarding (Basic, API Key, or OAuth2 Client Credentials). |
HTTP Body
The webhook HTTP body contains only the ciphertext (Base64).
Example:
BEm45DWVy4/7cVsrEgGP6XZGaCnZUcLotXgu70lXXFbeKSak1QMPKejnZ17yFgLmAJscuAf4o7Y=Internal JSON Payload (before encryption)
This is the structure you will receive after decrypting the ciphertext using:
encryptionKeyX-IVX-AuthTag
Your system must perform the decryption locally.
Example: payment.succeeded
{
"eventId": "a8ca3d79-c28d-4302-9414-b3433f6d40ec",
"eventType": "payment.succeeded",
"timestamp": "2024-11-04T18:45:23Z",
"paymentStatus": "Succeeded",
"error": null
}Example: payment.declined
{
"eventId": "b9db4e80-d39e-4413-9525-c4544f7d51fd",
"eventType": "payment.declined",
"timestamp": "2024-11-04T18:46:12Z",
"paymentStatus": "Declined",
"error": {
"code": "DECLINED",
"message": "The payment was rejected by the payer."
}
}Field Definitions
| Field | Description |
|---|---|
| eventId | Internal unique identifier used to correlate logs in case of issues. |
| eventType | Event type (payment.succeeded, payment.declined, payment.failed, payment.expired). |
| timestamp | ISO-8601 UTC timestamp when the event occurred. |
| paymentStatus | Final normalized status: Pending, Succeeded, Declined, Expired, Failed. |
| error | Object containing code and message when the payment is unsuccessful. |
Decryption Responsibilities
Your system must:
Read:
X-IVX-AuthTag- Webhook ciphertext (body)
encryptionKeystored during the intent creation
Perform AES-256/GCM decryption
- Validate the resulting JSON
Fidelidade does not send plaintext payloads through webhook.
Reference Java Decryption Example
This is a minimal example showing how partners can decrypt webhook payloads using AES-256/GCM.
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
public class ShortAesGcmDecryptExample {
public static void main(String[] args) throws Exception {
String encryptionKey = "<BASE64_ENCRYPTION_KEY>";
String ivBase64 = "<X-IV>";
String tagBase64 = "<X-AuthTag>";
String ciphertextBase64 = "<WEBHOOK_CIPHERTEXT>";
byte[] keyBytes = Base64.getDecoder().decode(encryptionKey);
byte[] iv = Base64.getDecoder().decode(ivBase64);
byte[] tag = Base64.getDecoder().decode(tagBase64);
byte[] cipherBytes = Base64.getDecoder().decode(ciphertextBase64);
byte[] combined = new byte[cipherBytes.length + tag.length];
System.arraycopy(cipherBytes, 0, combined, 0, cipherBytes.length);
System.arraycopy(tag, 0, combined, cipherBytes.length, tag.length);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec spec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBytes, "AES"), spec);
String json = new String(cipher.doFinal(combined), "UTF-8");
System.out.println("Decrypted JSON: " + json);
}
}This logic can be replicated in any language (Node.js, Python, .NET, Go, PHP, Rust, etc.) that supports AES-GCM.
Best Practices
- Always generate a new IV for each encryption operation (the platform already does this for webhooks).
- Store the pair Idempotency-Key + encryptionKey for all payment intents.
- Use eventId to guarantee idempotent processing on your side.
- Return 200 OK as soon as you receive the webhook (process internally afterwards).
- Reject any webhook that:
- fails authentication
- contains invalid Base64
- fails AES-GCM integrity validation
- Never attempt to read or interpret the
receiptToken.